﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using SportsStore.Domain;

namespace MK.DataAccess
{
    public interface IRepository<T> : IDisposable where T : class
    {
        void SetContext<TOther>(IRepository<TOther> repository) where TOther : class;
        void ResetContext();
        DbContext GetContext();
        IQueryable<T> All(params Expression<Func<T, object>>[] includes);
        IQueryable<T> Filter(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes);

        IQueryable<T> Filter(Expression<Func<T, bool>> filter,
            out int total, int index = 0, int size = 50, params Expression<Func<T, object>>[] includes);

        bool Contains(Expression<Func<T, bool>> predicate);

        T Find(params object[] keys);

        T Find(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes);

        T Create(T t);

        int Delete(T t);

        int Delete(Expression<Func<T, bool>> predicate);

        int Update(T t);
        int Update();
        int Count { get; }
    }

    public class Repository<T> :  IRepository<T>
        where T : class
    {
        protected DbContext Context = null;
        private bool shareContext = false;


        public Repository()
        {
            Context = new PowerContext();
        }
        public Repository(DbContext context)
        {
            Context = context;
        }

        protected DbSet<T> DbSet
        {
            get
            {
                return Context.Set<T>();
            }
        }

        public void Dispose()
        {
            if (shareContext && (Context != null))
                Context.Dispose();
        }

        public void SetContext<TOther>(IRepository<TOther> repository) where TOther : class
        {
            this.Context = repository.GetContext();
        }

        public void ResetContext()
        {
            this.Context = new PowerContext();
        }

        public DbContext GetContext()
        {
            return this.Context;
        }
        public virtual IQueryable<T> All(params Expression<Func<T, object>>[] includes)
        {
            var dbSet = GetInclude(DbSet, includes);
            return dbSet.AsQueryable();
        }

        public virtual IQueryable<T> Filter(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes)
        {           

            var dbSet = GetInclude(DbSet.Where(predicate), includes);

            return dbSet.AsQueryable<T>();
        }

        private IQueryable<T> GetInclude(IQueryable<T> dbSet, params Expression<Func<T, object>>[] includes)
        {
            if (includes.Length > 0)
            {
                foreach (var expression in includes)
                {
                    dbSet = dbSet.Include(expression);
                }
            }
            return dbSet;
        }

        public virtual IQueryable<T> Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50, params Expression<Func<T, object>>[] includes)
        {
          
            var skipCount = index * size;

            var resetSet = filter != null ? DbSet.Where(filter).AsQueryable() :
                DbSet.AsQueryable();

            resetSet = GetInclude(resetSet, includes);

            total = resetSet.Count();

            resetSet = skipCount == 0 ? resetSet.Take(size) :
                resetSet.Skip(skipCount).Take(size);

            return resetSet.AsQueryable();
        }

        public bool Contains(Expression<Func<T, bool>> predicate)
        {
          
            return DbSet.Count(predicate) > 0;
        }

        public virtual T Find(params object[] keys)
        {
          
            return DbSet.Find(keys);
        }

        public virtual T Find(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes)
        {
           
            var dbSet = GetInclude(DbSet.Where(predicate), includes);
            return dbSet.FirstOrDefault();
        }

        public virtual T Create(T TObject)
        {
            var newEntry = DbSet.Add(TObject);
            if (!shareContext)
                Context.SaveChanges();
            return newEntry;
        }
        public virtual List<T> Create(List<T> objects)
        {
            List<T> result = objects.Select(obj => DbSet.Add(obj)).ToList();
            if (!shareContext)
                Context.SaveChanges();
            return result;
        }
        int IRepository<T>.Delete(T t)
        {
            DbSet.Remove(t);
            return Context.SaveChanges();
        }

        public int Update()
        {
            return Context.SaveChanges();
        }

        public virtual int Count
        {
            get
            {
                return DbSet.Count();
            }
        }


        public virtual int Delete(T TObject)
        {
            DbSet.Remove(TObject);
            if (!shareContext)
                return Context.SaveChanges();
            return 0;
        }

        public virtual int Update(T TObject)
        {
            var entry = Context.Entry(TObject);
            DbSet.Attach(TObject);
            entry.State = EntityState.Modified;
            if (!shareContext)
                return Context.SaveChanges();
            return 0;
        }

        public virtual int Delete(Expression<Func<T, bool>> predicate)
        {
          
            var objects = Filter(predicate);
            foreach (var obj in objects)
                DbSet.Remove(obj);
            if (!shareContext)
                return Context.SaveChanges();
            return 0;
        }

    }

}